iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0

ELF
今天要來實作ELF Loader,那什麼是ELF呢,我們又為什麼要做ELF Loader呢?
ELF指的是可執行與可鏈結格式(Executable and Linkable Format),是一種常見的二進制文件格式,用於在Linux和Unix等作業系統中存儲可執行程序、共享庫(動態連結庫)和核心轉儲文件。ELF 格式是這些作業系統上的主要可執行文件格式,它提供了一個標準的結構,用於將代碼和數據組織成一個可執行的程序或共享庫。

簡單來說,所謂的ELF就是在Unix的世界中,我們Windows所常見的OOO.exe這種檔案類型。那為什麼我們要實作ELF Loader的答案也呼之欲出-我們要讓模擬器具備直接開啟ELF,執行的能力。

我們今天就是要來實作這個功能。

ELF Loader

那具體而言要怎麼實做呢,我們將處理器要執行一個程式需要的資料分為指令(instruction)以及資料(Data);以指令來說,處理器來在執行的時候會有Program Counter(PC),PC的位置即為我們所要執行的指令所在的記憶體位置。而以資料來說,我們通常是透過跟記憶體相關的指令,例如Load, Store來讀取,而讀取時我們也會知道其所在的記憶體位置。結合上面兩點,我們可以得知無論是指令還是資料,我們只要把它放到記憶體就好了。

那我們如果要這麼做,是直接fopen(elf)之後將他放進一個記憶體空間這麼簡單嗎? 答案是否定的,ELF本身除了指令及資料以外,還包含了給Linker等工具的資訊 (如下圖,只有Loadable的部分是我們需要的),而這些資訊實際上是處理器所不需要的,因此我們要有能力區分,僅將我們需要的資料放到記憶體中。(非常推薦閱讀"程式設計師的自我修養",對於ELF的理解有很大的幫助)

ELF
圖源

ELF Loader 實作

首先我們根據TDD的原理,我們要先寫一個測試。測試的輸入為一個elf file的路徑,而輸出為該file的entry point (第一道指令位置)。

要怎麼知道該elf的entry point為誰呢,我們可以透過readelf來看,

riscv64-unknown-elf-readelf -a HelloWorld.elf  > HelloWorld.readelf

以我產生的HelloWorld為例,我們看到entry point為0x10116,如下圖。
https://ithelp.ithome.com.tw/upload/images/20230927/20162436JEsee7Iw3w.png
因此我們的Test內容如下

TEST(MyTestSuite, ELFLoaderTest) {
    const char* test = "test.elf";
    ALISS::loadElf(test);
    EXPECT_EQ(ALISS::pc, 0x10116);
}

當loadELF執行完後,我們應該將PC位置設定為0x10116,而EXPECT_EQ就是會去測兩者輸入是否相等,而目前測試結果當然是錯誤,在這邊就不呈現。

接下來我們來實作loadElf的第一個功能,讀Entry point,程式碼如下:

#include <elf.h>

void ALISS::loadElf(const char* filename)
{
    // ELF loader function
    std::ifstream file(filename, std::ios::binary);
    if (!file) {
        std::cerr << "Failed to open file: " << filename << std::endl;
        return;
    }
    // Read the ELF header
    Elf64_Ehdr elfHeader;
    file.read(reinterpret_cast<char*>(&elfHeader), sizeof(Elf64_Ehdr));
    // Check ELF magic number
    if (memcmp(elfHeader.e_ident, ELFMAG, SELFMAG) != 0) {
        std::cerr << "Not a valid ELF file: " << filename << std::endl;
        return;
    }
    // Check ELF class (32-bit or 64-bit)
    if (elfHeader.e_ident[EI_CLASS] != ELFCLASS64) {
        std::cerr << "Only 64-bit ELF files are supported: " << filename << std::endl;
        return;
    }
    // Check ELF data encoding (little-endian or big-endian)
    if (elfHeader.e_ident[EI_DATA] != ELFDATA2LSB) {
        std::cerr << "Only little-endian ELF files are supported: " << filename << std::endl;
        return;
    }
    // Get the entry point address
    Elf64_Addr entryPoint = elfHeader.e_entry;
    ALISS::pc = entryPoint;
    
    // Add more code here to load and work with program segments, sections, etc.
    file.close();
	return;
}

這段code會先檢查magic number (判斷是否是ELF file),判斷格式後將pc讀出來,由於我們使用elf.h,entry point的位置就在elfHeader.e_entry,非常容易得到。

實現後再重新進行測試,確認測試通過,送出。
https://ithelp.ithome.com.tw/upload/images/20230927/20162436S0t8pUtnOy.png


碎碎念 : 今天大概介紹 elf file是什麼,並套用elf.h得到了entry point,明天來將Loadable的部分取出來。


上一篇
Day 11 - TDD, GTest & CTest
下一篇
Day 13 - ELF Loader (2/2)
系列文
從零開始的RISC-V ISA Simulator (Another Little RISC-V ISA Simulator)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言